/*
 * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package jsr203.sun.nio.fs;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import jsr203.nio.file.FileRef;
import jsr203.nio.file.ProviderMismatchException;
import jsr203.nio.file.attribute.DosFileAttributeView;
import jsr203.nio.file.attribute.DosFileAttributes;
import static jsr203.sun.nio.fs.UnixConstants.ENODATA;
import static jsr203.sun.nio.fs.UnixNativeDispatcher.close;
import sun.misc.Unsafe;

/**
 * Linux implementation of DosFileAttributeView for use on file systems such
 * as ext3 that have extended attributes enabled and SAMBA configured to store
 * DOS attributes.
 */

class LinuxDosFileAttributeView
    implements DosFileAttributeView
{
    private static final Unsafe unsafe = CopyMemory.getUnsafe();

    private static final String DOS_XATTR_NAME = "user.DOSATTRIB";
    private static final byte[] DOS_XATTR_NAME_AS_BYTES = DOS_XATTR_NAME.getBytes();

    private static final int DOS_XATTR_READONLY = 0x01;
    private static final int DOS_XATTR_HIDDEN   = 0x02;
    private static final int DOS_XATTR_SYSTEM   = 0x04;
    private static final int DOS_XATTR_ARCHIVE  = 0x20;

    // encapsulates the object that we are bound too
    private volatile FileInfo fileInfo;

    private FileInfo getFileInfo() {
        FileInfo res = fileInfo;
        if (res == null)
            throw new IllegalStateException();
        return fileInfo;
    }

    @Override
    public DosFileAttributeView bind(FileRef obj) {
        return bind(obj, true);
    }

    @Override
    public DosFileAttributeView bind(FileRef obj, boolean followLinks) {
        if (obj != null && !(obj instanceof UnixPath))
            throw new ProviderMismatchException();
        fileInfo = (obj == null) ? null : new FileInfo(obj, followLinks);
        return this;
    }

    @Override
    public DosFileAttributes readAttributes() throws IOException {
        FileInfo info = getFileInfo();
        UnixPath file = (UnixPath)info.file();
        file.checkRead();

        int fd = file.openForAttributeAccess(info.followLinks());
        try {
             final UnixFileAttributes attrs = UnixFileAttributes.get(fd);
             final int dosAttribute = getDosAttribute(fd);

             return new DosFileAttributes() {
                @Override
                public long lastModifiedTime() {
                    return attrs.lastModifiedTime();
                }
                @Override
                public long lastAccessTime() {
                    return attrs.lastAccessTime();
                }
                @Override
                public long creationTime() {
                    return attrs.creationTime();
                }
                @Override
                public TimeUnit resolution() {
                    return attrs.resolution();
                }
                @Override
                public boolean isRegularFile() {
                    return attrs.isRegularFile();
                }
                @Override
                public boolean isDirectory() {
                    return attrs.isDirectory();
                }
                @Override
                public boolean isSymbolicLink() {
                    return attrs.isSymbolicLink();
                }
                @Override
                public boolean isOther() {
                    return attrs.isOther();
                }
                @Override
                public long size() {
                    return attrs.size();
                }
                @Override
                public int linkCount() {
                    return attrs.linkCount();
                }
                @Override
                public Object fileKey() {
                    return attrs.fileKey();
                }
                @Override
                public boolean isReadOnly() {
                    return (dosAttribute & DOS_XATTR_READONLY) != 0;
                }
                @Override
                public boolean isHidden() {
                    return (dosAttribute & DOS_XATTR_HIDDEN) != 0;
                }
                @Override
                public boolean isArchive() {
                    return (dosAttribute & DOS_XATTR_ARCHIVE) != 0;
                }
                @Override
                public boolean isSystem() {
                    return (dosAttribute & DOS_XATTR_SYSTEM) != 0;
                }
             };

        } catch (UnixException x) {
            x.rethrowAsIOException(file);
            return null;    // keep compiler happy
        } finally {
            close(fd);
        }
    }

    @Override
    public LinuxDosFileAttributeView setTimes(Long lastModifiedTime,
                                              Long lastAccessTime,
                                              Long createTime, // ignore
                                              TimeUnit unit)
        throws IOException
    {
        FileInfo info = getFileInfo();
        UnixFileAttributeView.createBasicView()
            .bind(info.file(), info.followLinks())
            .setTimes(lastModifiedTime, lastAccessTime, createTime, unit);
        return this;
    }

    @Override
    public DosFileAttributeView setReadOnly(boolean value) throws IOException {
        updateDosAttribute(DOS_XATTR_READONLY, value);
        return this;
    }

    @Override
    public DosFileAttributeView setHidden(boolean value) throws IOException {
        updateDosAttribute(DOS_XATTR_HIDDEN, value);
        return this;
    }

    @Override
    public DosFileAttributeView setArchive(boolean value) throws IOException {
        updateDosAttribute(DOS_XATTR_ARCHIVE, value);
        return this;
    }

    @Override
    public DosFileAttributeView setSystem(boolean value) throws IOException {
        updateDosAttribute(DOS_XATTR_SYSTEM, value);
        return this;
    }

    /**
     * Reads the value of the user.DOSATTRIB extended attribute
     */
    private int getDosAttribute(int fd) throws UnixException {
        final int size = 24;

        NativeBuffer buffer = NativeBuffers.getNativeBuffer(size);
        try {
            int len = LinuxNativeDispatcher
                .fgetxattr(fd, DOS_XATTR_NAME_AS_BYTES, buffer.address(), size);

            if (len > 0) {
                // ignore null terminator
                if (unsafe.getByte(buffer.address()+len-1) == 0)
                    len--;

                // convert to String and parse
                byte[] buf = new byte[len];
                CopyMemory.toByteArray(buffer.address(), buf, len);
                // unsafe.copyMemory(null, buffer.address(), buf,
                //    Unsafe.ARRAY_BYTE_BASE_OFFSET, len);
                String value = new String(buf); // platform encoding

                // should be something like 0x20
                if (value.length() >= 3 && value.startsWith("0x")) {
                    try {
                        return Integer.parseInt(value.substring(2), 16);
                    } catch (NumberFormatException x) {
                        x.printStackTrace();
                    }
                }
            }
            throw new UnixException("Value of " + DOS_XATTR_NAME + " attribute is invalid");
        } catch (UnixException x) {
            // default value when attribute does not exist
            if (x.errno() == ENODATA)
                return 0;
            throw x;
        } finally {
            buffer.release();
        }
    }

    /**
     * Updates the value of the user.DOSATTRIB extended attribute
     */
    private void updateDosAttribute(int flag, boolean enable) throws IOException {
        FileInfo info = getFileInfo();
        UnixPath file = (UnixPath)info.file();
        file.checkWrite();

        int fd = file.openForAttributeAccess(info.followLinks());
        try {
            int oldValue = getDosAttribute(fd);
            int newValue = oldValue;
            if (enable) {
                newValue |= flag;
            } else {
                newValue &= ~flag;
            }
            if (newValue != oldValue) {
                byte[] value = ("0x" + Integer.toHexString(newValue)).getBytes();
                NativeBuffer buffer = NativeBuffers.asNativeBuffer(value);
                try {
                    LinuxNativeDispatcher.fsetxattr(fd, DOS_XATTR_NAME_AS_BYTES,
                        buffer.address(), value.length+1);
                } finally {
                    buffer.release();
                }
            }
        } catch (UnixException x) {
            x.rethrowAsIOException(file);
        } finally {
            close(fd);
        }
    }
}
